package com.onlyxiahui.common.action.description.handler.impl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.onlyxiahui.common.action.description.DocumentContext;
import com.onlyxiahui.common.action.description.annotation.DocField;
import com.onlyxiahui.common.action.description.annotation.DocTitle;
import com.onlyxiahui.common.action.description.bean.PropertyData;
import com.onlyxiahui.common.action.description.box.JsonDataTypeBox;
import com.onlyxiahui.common.action.description.common.TagConstant;
import com.onlyxiahui.common.action.description.handler.PropertyHandler;
import com.onlyxiahui.common.action.description.handler.PropertyValidateHandler;
import com.onlyxiahui.common.action.description.handler.data.PropertyCascade;
import com.onlyxiahui.common.action.description.util.ActionBeanPropertyBox;
import com.onlyxiahui.common.action.description.util.ActionClassUtil;
import com.onlyxiahui.common.action.description.util.ActionDescriptionUtil;
import com.onlyxiahui.common.action.description.util.ActionReflectUtil;
import com.onlyxiahui.common.action.description.util.reflect.OnlyFieldTypeUtil;
import com.onlyxiahui.common.action.description.util.reflect.OnlyTypeUtils;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;

/**
 * Description <br>
 * Date 2020-01-07 17:50:39<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public abstract class BasePropertyHandler implements PropertyHandler {

	ActionBeanPropertyBox actionBeanPropertyBox = new ActionBeanPropertyBox();

	@Override
	public boolean support(DocumentContext context, Class<?> propertyClass, Type type) {
		return true;
	}

	@Override
	public List<PropertyData> handle(DocumentContext context, Class<?> propertyClass, Type type) {
		PropertyCascade pc = new PropertyCascade(propertyClass);
		List<PropertyData> list = list(context, pc, propertyClass, type);
		return list;
	}

	public boolean ignore(DocumentContext context, Class<?> classType, Field field) {
		return context.getIgnoreJudgeBox().ignore(context, classType, field);
	}

	public List<PropertyData> list(DocumentContext context, PropertyCascade pc, Class<?> propertyClass, Type type) {

		List<PropertyData> list = new ArrayList<>();

		if (ActionClassUtil.isPrimitive(propertyClass) || ActionClassUtil.isBaseProperty(propertyClass)) {

		} else if (Collection.class.isAssignableFrom(propertyClass) && type != null) {
			// 集合类型
			list = collectionParameterizedType(context, pc, propertyClass, type);
		} else if (ActionClassUtil.isMap(propertyClass) && type != null) {
			list = mapParameterizedType(context, pc, propertyClass, type);
		} else if (propertyClass.isArray()) {
			list = array(context, pc, propertyClass, type);
		} else if (ActionClassUtil.isBean(propertyClass)) {
			try {
				list = bean(context, pc, propertyClass, type);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	private List<PropertyData> bean(DocumentContext context, PropertyCascade pc, Class<?> propertyClass, Type type) {
		List<PropertyData> list = new ArrayList<>();
		// String className = propertyClass.getName();
		boolean isParameterizedType = (type instanceof ParameterizedType);

		List<PropertyDescriptor> propertyList = actionBeanPropertyBox.getWritePropertyList(propertyClass);
		for (PropertyDescriptor pd : propertyList) {
			// Class<?> clazz = pd.getPropertyType();
			String name = pd.getName();

			Field field = ActionReflectUtil.getField(propertyClass, Object.class, name);

			if (null != field && !ignore(context, propertyClass, field)) {
				// 得到field的class及类型全路径
				Class<?> fieldClass = field.getType();
				// 关键的地方，如果是List类型，得到其Generic的类型
				Type fieldType = field.getGenericType();

				if (isParameterizedType) {

					Type ft = OnlyFieldTypeUtil.getFieldType(propertyClass, type, fieldType);
					if (ft != null) {
						fieldClass = OnlyTypeUtils.getClass(ft);
						fieldType = ft;
					}
				}

				String example = getFieldExample(context, propertyClass, field);
				String description = getFieldDescription(context, propertyClass, field);
				name = getFieldName(context, propertyClass, field);

				PropertyData data = new PropertyData();
				data.setName(name);
				data.setType(JsonDataTypeBox.getType(fieldClass));
				data.setDescription(description);
				data.setExample(example);
				list.add(data);

				if (!pc.isSuperPropertyClass(fieldClass)) {
					List<PropertyData> nodes = list(context, new PropertyCascade(fieldClass, pc), fieldClass, fieldType);
					data.setNodes(nodes);
				}

				// 生成字段验证信息
				for (PropertyValidateHandler h : context.getPropertyValidateHandlers()) {
					if (h.support(context, data, propertyClass, type, field)) {
						h.handle(context, data, propertyClass, type, field);
					}
				}
			}
		}
		return list;
	}

	private List<PropertyData> array(DocumentContext context, PropertyCascade pc, Class<?> propertyClass, Type propertyType) {
		List<PropertyData> list = new ArrayList<>();

		Type fieldType = null;
		if (propertyType instanceof GenericArrayType) {
			fieldType = ((GenericArrayType) propertyType).getGenericComponentType();
		}
		// GenericArrayType
		Class<?> genericClass = propertyClass.getComponentType();

		String name = "";
		String type = JsonDataTypeBox.getType(genericClass);
		String className = genericClass.getName();
		String description = getClassDescription(context, genericClass);

		PropertyData data = new PropertyData();

		List<PropertyData> nodes = list(context, pc, genericClass, fieldType);

		data.setName(name);
		data.setType(type);
		data.setClassName(className);
		data.setDescription(description);

		data.setNodes(nodes);

		list.add(data);
		return list;
	}

	private List<PropertyData> mapParameterizedType(DocumentContext context, PropertyCascade pc, Class<?> propertyClass, Type propertyType) {
		List<PropertyData> list = new ArrayList<>();
		if (propertyType instanceof ParameterizedType) {
//			PropertyData data = new PropertyData();
//			data.setType(JsonDataTypeBox.getType(propertyClass));
//			data.setName("mapKey");
//			data.setDescription("");
//			data.setClassName("map");
//
//			list.add(data);

			ParameterizedType parameterizedType = (ParameterizedType) propertyType;
			Type[] types = parameterizedType.getActualTypeArguments();
			if (types.length > 1) {
				// Type keyType = types[0];
				Type valueType = types[1];
				if (valueType instanceof Class<?>) {
					Class<?> valueGenericClass = (Class<?>) valueType;

					String name = "mapKey";
					String type = JsonDataTypeBox.getType(valueGenericClass);
					String className = valueGenericClass.getName();
					String description = getClassDescription(context, valueGenericClass);

					List<PropertyData> nodes = list(context, new PropertyCascade(valueGenericClass, pc), valueGenericClass, null);

					PropertyData fieldData = new PropertyData();
					fieldData.setName(name);
					fieldData.setType(type);
					fieldData.setClassName(className);
					fieldData.setDescription(description);

					fieldData.setNodes(nodes);

					// List<PropertyData> ns = new ArrayList<>();
					list.add(fieldData);
					// data.setNodes(ns);
				} else if (valueType instanceof ParameterizedType) {
					ParameterizedType fieldType = (ParameterizedType) valueType;
					Type rawType = fieldType.getRawType();
					if (rawType instanceof Class) {
						Class<?> valueGenericClass = (Class<?>) rawType;

						String name = "mapKey";
						String type = JsonDataTypeBox.getType(valueGenericClass);
						String className = valueGenericClass.getName();
						String description = getClassDescription(context, valueGenericClass);

						List<PropertyData> nodes = list(context, pc, valueGenericClass, fieldType);

						PropertyData fieldData = new PropertyData();
						fieldData.setName(name);
						fieldData.setType(type);
						fieldData.setClassName(className);
						fieldData.setDescription(description);

						fieldData.setNodes(nodes);

						// List<PropertyData> ns = new ArrayList<>();
						list.add(fieldData);
						// data.setNodes(ns);
					}
				}
			}
		}
		return list;
	}

	private List<PropertyData> collectionParameterizedType(DocumentContext context, PropertyCascade pc, Class<?> propertyClass, Type propertyType) {
		List<PropertyData> list = new ArrayList<>();
		if (propertyType instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) propertyType;
			// Type fieldType = parameterizedType.getRawType();
			Type[] types = parameterizedType.getActualTypeArguments();
			if (types.length > 0) {
				Type pt = types[0];
				if (pt instanceof Class<?>) {
					Class<?> genericClass = (Class<?>) pt;

					String name = "";
					String type = JsonDataTypeBox.getType(genericClass);
					String className = genericClass.getName();
					String description = getClassDescription(context, genericClass);

					List<PropertyData> nodes = list(context, new PropertyCascade(genericClass, pc), genericClass, null);

					PropertyData fieldData = new PropertyData();
					fieldData.setName(name);
					fieldData.setType(type);
					fieldData.setClassName(className);
					fieldData.setDescription(description);

					fieldData.setNodes(nodes);

					list.add(fieldData);

				} else if (pt instanceof ParameterizedType) {
					ParameterizedType fieldType = (ParameterizedType) pt;
					Type rawType = fieldType.getRawType();
					if (rawType instanceof Class) {
						Class<?> genericClass = (Class<?>) rawType;

						String name = "";
						String type = JsonDataTypeBox.getType(genericClass);
						String className = genericClass.getName();
						String description = getClassDescription(context, genericClass);

						List<PropertyData> nodes = list(context, pc, genericClass, fieldType);

						PropertyData fieldData = new PropertyData();
						fieldData.setName(name);
						fieldData.setType(type);
						fieldData.setClassName(className);
						fieldData.setDescription(description);

						fieldData.setNodes(nodes);

						list.add(fieldData);
					}
				}
			}
		}
		return list;
	}

	public JavaField getJavaField(DocumentContext context, Class<?> beanClass, Class<?> rootClass, String name) {
		JavaField jf = getJavaField(context, beanClass, name);

		// 如果字段不存在，并且顶层rootClass不等于当前
		if (null == jf && beanClass != rootClass) {
			Class<?> superClass = beanClass.getSuperclass();
			jf = getJavaField(context, superClass, name);
			boolean isRoot = superClass == rootClass;
			while (!isRoot && null == jf) {
				superClass = superClass.getSuperclass();
				jf = getJavaField(context, superClass, name);
				isRoot = superClass == rootClass;
			}
		}
		return jf;
	}

	public JavaField getJavaField(DocumentContext context, Class<?> beanClass, String name) {
		String className = beanClass.getName();
		JavaField jf = context.getSourceBox().getJavaFieldByName(className, name);
		return jf;
	}

	public String getFieldName(DocumentContext context, Class<?> beanClass, Field field) {

		String name = field.getName();

		DocField docField = field.getAnnotation(DocField.class);

		String fieldName = null;
		if (null != docField) {
			fieldName = docField.name();
		}

		if (null == fieldName || fieldName.isEmpty()) {
			JavaField jf = getJavaField(context, beanClass, Object.class, name);
			if (null != jf) {
				DocletTag t = jf.getTagByName(TagConstant.DOC_NAME);
				if (null != t) {
					String v = t.getValue();
					if (null != v && !v.isEmpty()) {
						fieldName = v;
					}
				}
			}
		}
		return (null == fieldName || fieldName.isEmpty()) ? name : fieldName;
	}

	public String getFieldExample(DocumentContext context, Class<?> beanClass, Field field) {
		String name = field.getName();

		DocField docField = field.getAnnotation(DocField.class);

		String example = null;
		if (null != docField) {
			example = docField.example();
		}

		if (null == example || example.isEmpty()) {
			JavaField jf = getJavaField(context, beanClass, Object.class, name);
			if (null != jf) {
				DocletTag e = jf.getTagByName(TagConstant.DOC_EXAMPLE);
				if (null != e) {
					String v = e.getValue();
					if (null != v && !v.isEmpty()) {
						example = v;
					}
				}
			}
		}
		return null == example ? "" : example;
	}

	public String getFieldDescription(DocumentContext context, Class<?> beanClass, Field field) {
		String name = field.getName();
		JavaField jf = getJavaField(context, beanClass, Object.class, name);
		DocField docField = field.getAnnotation(DocField.class);
		DocTitle docTitle = field.getAnnotation(DocTitle.class);
		return ActionDescriptionUtil.getDescription(docField, docTitle, jf);
	}

	public String getClassDescription(DocumentContext context, Class<?> beanClass) {
		String className = beanClass.getName();
		JavaClass jc = context.getSourceBox().getJavaClassByName(className);
		DocTitle docTitle = beanClass.getAnnotation(DocTitle.class);
		return ActionDescriptionUtil.getDescription(docTitle, jc);
	}
}
